﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Glodon.Gdmp.DB;
using Glodon.Gdmp.UI;

namespace Glodon.Gdmp.SDK.Samples.ElementFilters
{
    /// <summary>
    /// 此示例展示了各种过滤器的使用，对文档中的墙内实体进行位置和类别检查。
    /// </summary>
    public class ElementFiltersCmd : IExternalCommand
    {
        private Document _document;

        public Result Execute(UIDocument uiDocument)
        {
            try
            {
                UIApplication app = UIApplication.Get();
                string location = Assembly.GetExecutingAssembly().Location;
                string sampleFilePath = Path.Combine($"{ Path.GetDirectoryName(location)}", "ElementFiltersSample.glab");
                //文档从未打开时运行，或者运行后关闭文档重新运行，需要打开文档
                if (_document == null || !_document.IsValidObject())
                {
                    var newUIDocument = app.OpenAndActiveDocument(sampleFilePath);
                    //保存文档对象，下次运行时，如果文档还未关闭，可以复用
                    _document = newUIDocument.Document;
                }

                // 获取所有的FamilyInstance。
                ElementCollector allInstancesCollector = new ElementCollector(_document).OfClass(typeof(FamilyInstance));
                // 获取所有点式构件类别的实例
                ElementCollector allPointFamilyCollector = allInstancesCollector.OfCategoryId(BuiltInCategory.BICU_POINT_FAMILY);

                // 重置收集器allInstancesCollector。
                allInstancesCollector = new ElementCollector(_document).OfClass(typeof(FamilyInstance));
                // 通过排除过滤器获取所有非点式构件类别的实例。
                ExclusionFilter nonPointFamilyFilter = new ExclusionFilter(allPointFamilyCollector.Select(e => e.Id).ToList());
                ElementCollector allNonPointFamilyInstancesCollector = allInstancesCollector.WherePasses(nonPointFamilyFilter);

                int wallCount = 0;
                List<ElementFilter> intersectFilters = new List<ElementFilter>();
                foreach (var element in allNonPointFamilyInstancesCollector)
                {
                    bool? isWall = element.Category?.Name.Equals("结构墙");
                    if (isWall != null && (bool)isWall)
                    {
                        wallCount++;

                        BodyIntersectsFilter filter = new BodyIntersectsFilter(GetBody(element));
                        intersectFilters.Add(filter);
                    }
                }

                MessageBoard.Show($"墙的个数：{wallCount}", "GDMPLab");

                // 使用逻辑"或"过滤器获取与墙相交的图元。
                LogicalOrFilter orFilter = new LogicalOrFilter(intersectFilters);
                ElementCollector allIntersectWallInstances = allPointFamilyCollector.WherePasses(orFilter);
                MessageBoard.Show($"与墙相交的图元个数：{allIntersectWallInstances.Count()}", "GDMPLab");

                // 检查完全在桌子下方的图元。                  
                allInstancesCollector = new ElementCollector(_document).OfClass(typeof(FamilyInstance));
                IEnumerable<Element> allDesks = allInstancesCollector.Where(e=>(e as FamilyInstance).Family.Name.Equals("桌子"));
                List<ElementFilter> underDeskFilters = new List<ElementFilter>();
                foreach (var desk in allDesks)
                {
                    var bbx = desk.GetBoundingBox();
                    var min = bbx.Min;
                    var max = bbx.Max;
                    // 适当减少纵向高度形成新包围盒以保证不会包含桌子本身。
                    var newmax = new Vector3d(max.X, max.Y, max.Z - 500);
                    var newBbx = new BoundingBox3d(min, newmax);
                    underDeskFilters.Add(new BoundingBoxIsInsideFilter(newBbx));
                }
                LogicalOrFilter underDeskOrFilter = new LogicalOrFilter(underDeskFilters);
                allInstancesCollector = new ElementCollector(_document).OfClass(typeof(FamilyInstance));
                IList<Element> underDeskInstances = allInstancesCollector.WherePasses(underDeskOrFilter).
                    Where(e => (e as FamilyInstance).Family.Name.Equals("球")).ToList();
                MessageBoard.Show($"所有桌子下方的图元个数：{underDeskInstances.Count()}", "GDMPLab");
                return Result.Succeeded;
            }
            catch (Exception)
            {
                return Result.Failed;
            }
        }

        #region 获取体。
        /// <summary>
        /// 获取图元的体。
        /// 从Element.Geometry获取几何的结果是图形节点集合。
        /// 每一个图形节点可能还是一个集合对象，如GraphicsNodeGroup，GraphicsElementShape等，
        /// 它们的基类都是GraphicsNode，需要继续遍历，直至找到GraphicsBody。
        /// </summary>
        /// <param name="element">图元。</param>
        /// <returns>图元的体。</returns>
        private Body GetBody(Element element)
        {
            Document document = element.Document;
            GraphicsElementShape graphicsElementShape = element.GetGeometry(null);
            if (graphicsElementShape == null) return null;
            IList<GraphicsNode> nodes = graphicsElementShape.GetNodes();
            IList<Body> bodies = new List<Body>();
            foreach (GraphicsNode node in nodes)
            {
                if (node is GraphicsBody)
                {
                    bodies.Add((node as GraphicsBody).Body);
                }
                if (node is GraphicsNodeGroup)
                {
                    GraphicsNodeGroup nodeGroup = node as GraphicsNodeGroup;
                    IList<GraphicsNode> groups = nodeGroup.GetNodes();
                    foreach (GraphicsNode n in groups)
                    {
                        if (n is GraphicsBody)
                        {
                            bodies.Add((n as GraphicsBody).Body);
                        }
                    }
                }
                if (node is GraphicsElementShape)
                {
                    GraphicsElementShape nodeShape = node as GraphicsElementShape;
                    IList<GraphicsNode> groups = nodeShape.GetNodes();
                    foreach (GraphicsNode n in groups)
                    {
                        if (n is GraphicsBody)
                        {
                            bodies.Add((n as GraphicsBody).Body);
                        }
                    }
                }
                if (node is GraphicsElementShapeReference)
                {
                    GraphicsElementShapeReference nodeShapeReference = node as GraphicsElementShapeReference;
                    ElementId referenceId = nodeShapeReference.ElementId;
                    Element referenceElem = document.GetElement(referenceId);
                    GraphicsElementShape rgeom = referenceElem.GetGeometry(null);
                    IList<GraphicsNode> referenceNodes = rgeom.GetNodes();
                    foreach (GraphicsNode n in referenceNodes)
                    {
                        if (n is GraphicsBody)
                        {
                            Body b = (n as GraphicsBody).Body;
                            Body newbody = b.CreateTransformed(nodeShapeReference.Transform);
                            bodies.Add(newbody);
                        }
                    }
                }
            }
            if (!bodies.Any()) return null;
            return bodies.OrderByDescending(x => x.Volume).FirstOrDefault();
        }
        #endregion
    }
}